"""
event_log_schema.py: Used to emit messages for Event Log
"""

import json

import jsl

########################################################
#   Schema Version
########################################################

"""
1.0.0
Initial version
1.1.0
- restructured event logger to decrease size of output files
- instead of fixed discriminating enum for events there is a 'id' number refering to enum in Properties class
- repeated strings (PassManager names and file paths) are also logged indirectly as a index into array
1.2.0
- source info is now an array in compilation warnings and errors
- source info now contains excerpt of code marked by file/line/column information
- compilation errors and warning contain optional appendix message
"""

major_version = 1
minor_version = 2
patch_version = 0


def get_schema_version():
    return "%s.%s.%s" % (str(major_version), str(minor_version), str(patch_version))


##########################


class SourceInfo(jsl.Document):
    f = jsl.IntField(
        required=True,
        description="Path to P4 source file, relative to manifest.json. This is index to file_ids array.",
    )
    l = jsl.IntField(required=True, description="Line - Index of line in file")
    c = jsl.IntField(required=True, description="Column - Index of first character on line")
    p = jsl.StringField(
        required=True, description="Excerpt of code pointed to by line/column information"
    )


class Event_Base(jsl.Document):
    class Options(object):
        definition_id = "Event_Base"
        inheritance_mode = jsl.ALL_OF
        additional_properties = True

    description = "Base class for events to derive from"
    # NOTE: time cannot be property of this class because in autogenerated C++ code
    # the attributes of super class come last in the constructor and if derived class
    # has optional attributes (which will have default value in the constructor) and
    # superclass has only mandatory ones, then generate_logging.py produces invalid C++ code.


class EventLoggerProperties(Event_Base):
    class Options(object):
        definition_id = "EventLoggerProperties"
        inheritance_mode = jsl.ALL_OF

    i = jsl.IntField(
        required=True, description="Id of event type. Use to index event_ids to get event name"
    )
    enabled = jsl.BooleanField(
        required=True,
        description="Flag indicating if event logger is enabled or disabled. Disabled event logger emits only this event.",
    )
    schema_version = jsl.StringField(
        required=True,
        enum=[get_schema_version()],
        description="Schema version used to produce this JSON.",
    )
    event_ids = jsl.ArrayField(
        required=True,
        min_items=9,
        max_items=9,
        items=jsl.StringField(
            required=True,
            enum=[
                "Properties",
                "Parse Error",
                "Compilation Error",
                "Compilation Warning",
                "Pass Changed",
                "Pipe Changed",
                "Iteration Changed",
                "Debug",
                "Decision",
            ],
        ),
        description="Mapping of numeric ids to actual event type names."
        "Id of the name is equal to index in this array",
    )
    manager_ids = jsl.ArrayField(
        required=True,
        items=jsl.StringField(required=True),
        description="Mapping of numeric ids to actual pass manager names."
        "Id of the name is equal to index in this array.",
    )
    file_ids = jsl.ArrayField(
        required=True,
        items=jsl.StringField(required=True),
        description="Mapping of numeric ids to actual file names."
        "Id of the name is equal to index in this array.",
    )
    start_time = jsl.StringField(
        required=True, description="Start time of compilation (POSIX timestamp)"
    )


class EventParserError(Event_Base):
    class Options(object):
        definition_id = "EventParserError"
        inheritance_mode = jsl.ALL_OF

    description = "Error which happens during parsing of P4 file"

    i = jsl.IntField(
        required=True, description="Id of event type. Use to index event_ids to get event name"
    )
    si = jsl.DocumentField("SourceInfo", required=True, as_ref=True, description="Source position")
    m = jsl.StringField(required=True, description="Message - reason for this error")
    t = jsl.IntField(required=True, description="Number of seconds since compilation started")


class EventCompilationError(Event_Base):
    class Options(object):
        definition_id = "EventCompilationError"
        inheritance_mode = jsl.ALL_OF

    description = "Error that occures after parsing, anyplace during compilation"

    i = jsl.IntField(
        required=True, description="Id of event type. Use to index event_ids to get event name"
    )
    si = jsl.ArrayField(
        items=jsl.DocumentField("SourceInfo", required=True, as_ref=True), required=False
    )
    cn = jsl.StringField(required=False, description="Well known error class name")
    m = jsl.StringField(required=True, description="Message - reason for this error")
    a = jsl.StringField(
        require=False,
        description="Optional message appendix. Used in typeChecking, contains some traceback information.",
    )
    t = jsl.IntField(required=True, description="Number of seconds since compilation started")


class EventCompilationWarning(Event_Base):
    class Options(object):
        definition_id = "EventCompilationWarning"
        inheritance_mode = jsl.ALL_OF

    description = "Warning that occures during compilation"

    i = jsl.IntField(
        required=True, description="Id of event type. Use to index event_ids to get event name"
    )
    si = jsl.ArrayField(
        items=jsl.DocumentField("SourceInfo", required=True, as_ref=True), required=False
    )
    cn = jsl.StringField(required=False, description="Well known warning class name")
    m = jsl.StringField(required=True, description="Message - reason for this warning")
    a = jsl.StringField(
        require=False,
        description="Optional message appendix. Used in typeChecking, contains some traceback information.",
    )
    t = jsl.IntField(required=True, description="Number of seconds since compilation started")


class EventDebug(Event_Base):
    class Options(object):
        definition_id = "EventDebug"
        inheritance_mode = jsl.ALL_OF

    description = "General purpose debug message"

    i = jsl.IntField(
        required=True, description="Id of event type. Use to index event_ids to get event name"
    )
    m = jsl.StringField(required=True, description="Message - debug message")
    f = jsl.IntField(
        required=True,
        description="Which C++ file emitted this event. Useful for constructing command line arguments",
    )
    v = jsl.IntField(required=True, description="Verbosity level of this particular message")
    t = jsl.IntField(required=True, description="Number of seconds since compilation started")


class EventDecision(Event_Base):
    class Options(object):
        definition_id = "EventDecision"
        inheritance_mode = jsl.ALL_OF

    description = "Event capturing compiler decision (eg.: during allocation)"

    i = jsl.IntField(
        required=True, description="Id of event type. Use to index event_ids to get event name"
    )
    m = jsl.StringField(required=True, description="Message - what is compiler deciding")
    d = jsl.StringField(required=True, description="What compiler decided to do")
    r = jsl.StringField(required=True, description="Why compiler decided to do it")
    f = jsl.IntField(
        required=True,
        description="Which C++ file emitted this event. Useful for constructing command line arguments",
    )
    v = jsl.IntField(required=True, description="Verbosity level of this particular message")
    t = jsl.IntField(required=True, description="Number of seconds since compilation started")


class EventPassChanged(Event_Base):
    class Options(object):
        definition_id = "EventPassChanged"
        inheritance_mode = jsl.ALL_OF

    description = (
        "Happens when new pass is run in current pass managers or when pass manager is changed"
    )

    i = jsl.IntField(
        required=True, description="Id of event type. Use to index event_ids to get event name"
    )
    mgr = jsl.IntField(
        required=True,
        description="Id of the pass manager. Refer to manager_ids for mapping to actual names.",
    )
    n = jsl.StringField(
        required=True, description="Name of pass being run withing particular pass manager"
    )
    s = jsl.IntField(
        required=True,
        description="Index of pass withing pass manager (which is an array of passes)",
    )
    t = jsl.IntField(required=True, description="Number of seconds since compilation started")


class EventIterationChanged(Event_Base):
    class Options(object):
        definition_id = "EventIterationChanged"
        inheritance_mode = jsl.ALL_OF

    description = "Happens when new PHV alloc/table placement iteration starts"

    i = jsl.IntField(
        required=True, description="Id of event type. Use to index event_ids to get event name"
    )
    num = jsl.IntField(
        required=True,
        description="Iteration number. Note that this increments between PHV alloc and table placement",
    )
    phase = jsl.StringField(
        required=True,
        enum=["phv_allocation", "table_placement"],
        description="Name of phase that is happening",
    )
    t = jsl.IntField(required=True, description="Number of seconds since compilation started")


class EventPipeProcessingStarted(Event_Base):
    class Options(object):
        definition_id = "EventPipeProcessingStarted"
        inheritance_mode = jsl.ALL_OF

    description = "Happens when particular pipe starts to be processed in backend"

    i = jsl.IntField(
        required=True, description="Id of event type. Use to index event_ids to get event name"
    )
    pipe = jsl.IntField(
        required=True, enum=[0, 1, 2, 3], description="Number of pipe that is now being processed"
    )
    t = jsl.IntField(required=True, description="Number of seconds since compilation started")


class Event_logJSONSchema(jsl.Document):
    # NOTE: This is here to ensure that C++ is generated properly but this class is not used to actually emit the JSON
    title = "EventLogSchema"
    description = "Event Log schema definition"

    schema_version = jsl.StringField(
        required=True,
        enum=[get_schema_version()],
        description="Schema version used to produce this JSON.",
    )

    # This has to be called `event_base` so generate_logging.py produces valid C++
    event_base = jsl.ArrayField(
        required=True,
        items=jsl.OneOfField(
            [
                jsl.DocumentField('EventLoggerProperties', as_ref=True),
                jsl.DocumentField('EventParserError', as_ref=True),
                jsl.DocumentField('EventCompilationError', as_ref=True),
                jsl.DocumentField('EventCompilationWarning', as_ref=True),
                jsl.DocumentField('EventDebug', as_ref=True),
                jsl.DocumentField('EventDecision', as_ref=True),
                jsl.DocumentField('EventPassChanged', as_ref=True),
                jsl.DocumentField('EventIterationChanged', as_ref=True),
                jsl.DocumentField('EventPipeProcessingStarted', as_ref=True),
            ]
        ),
    )
